
goog.provide('dk.cursorManager');

goog.require('goog.dom');
goog.require('goog.dom.NodeType');
goog.require('goog.style');


/**
 * @param {Element} contentEditableEl
 * @return {number}
 */
dk.cursorManager.getCursorPosition = function(contentEditableEl) {
  var domHelper = goog.dom.getDomHelper(contentEditableEl);
  var range = dk.cursorManager.getSelectionAsRange(domHelper.getDocument());
  try {
    range.setStart(contentEditableEl, 0);
    return range.toString().length;
  } catch (e) {
    output('Error maniuplating range: ' + e);
    return 0;
  }
};


/**
 * @param {Document} doc
 * @return {Range?}
 */
dk.cursorManager.getSelectionAsRange = function(doc) {
  // From http://www.quirksmode.org/dom/range_intro.html
  var userSelection = dk.cursorManager.getSelection(doc);
  if (userSelection.getRangeAt) {
    // Clone the range so that manipulating it does not affect the selection.
    return userSelection.getRangeAt(0).cloneRange();
  } else { // Safari!
    var range = doc.createRange();
    range.setStart(userSelection.anchorNode, userSelection.anchorOffset);
    range.setEnd(userSelection.focusNode, userSelection.focusOffset);
    return range;
  }
};


/**
 * @param {Document} doc
 * @return {RangeLike?}
 */
dk.cursorManager.getSelection = function(doc) {
  // From http://www.quirksmode.org/dom/range_intro.html
  var win = goog.dom.getWindow(doc);
  var userSelection = null;
  if (win.getSelection) {
    userSelection = win.getSelection();
  } else if (doc.selection) { // should come last; Opera!
    userSelection = doc.selection.createRange();
  }
  return userSelection;
};


/**
 * @param {Element} contentEditableEl
 */
dk.cursorManager.placeCursorAtEnd = function(contentEditableEl) {
  var lastTextNode = contentEditableEl.lastChild;
  while (lastTextNode && lastTextNode.nodeType != goog.dom.NodeType.TEXT) {
    lastTextNode = lastTextNode.previousSibling;
  }
  if (lastTextNode) {
    var domHelper = goog.dom.getDomHelper(contentEditableEl);
    var doc = domHelper.getDocument();
    var textLength = lastTextNode.nodeValue.length;
    var range = doc.createRange();
    range.setStart(lastTextNode, textLength);
    range.setEnd(lastTextNode, textLength);
    var selection = dk.cursorManager.getSelection(doc);
    selection.removeAllRanges();
    selection.addRange(range);
    // Need to call focus() explicitly upon updating the selection.
    contentEditableEl.focus();
  }
};


// TODO(mbolin): Determine where to put the cursor when moving between rows.

/**
 * @param {Element} contentEditableEl
 * @param {boolean} isTop true to check the top row; false to check the bottom row.
 * @return {boolean}
 */
dk.cursorManager.isCursorInRow = function(contentEditableEl, isTop) {
  var textNode = contentEditableEl.firstChild;
  if (textNode.nodeType != goog.dom.NodeType.TEXT) {
    return false;
  }

  var cursorPos = dk.cursorManager.getCursorPosition(contentEditableEl);
  var text = dk.cursorManager.nodeToText(contentEditableEl);
  if (cursorPos >= text.length && !isTop) {
    // If the cursor is at the end of the line, cursor must be in bottom row.
    return true;
  } else if (cursorPos == 0 && isTop) {
    // If the cursor is at the start of the line, cursor must be in top row.
    return true;
  }

  // Create a temporary DIV that should have the same dimensions as the original
  // contentEditableEl, except each character is wrapped in its own SPAN tag so
  // its position can be determined.
  var html = [];
  for (var i = 0; i < text.length; ++i) {
    var c = text.charAt(i);
    html.push('<span id=":' + i + '">' + c + '</span>');
  }
  var domHelper = goog.dom.getDomHelper(contentEditableEl);
  var div = domHelper.createElement(contentEditableEl.nodeName);
  div.style.position = 'absolute';
  div.style.top = '-10000px';
  div.style.left = '-10000px';
  div.style.width = goog.style.getSize(contentEditableEl).width + 'px';
  div.className = contentEditableEl.className;
  div.innerHTML = html.join('');
  domHelper.getDocument().body.appendChild(div);

  // Use the location of the span that corresponds to the cursor position to
  // determine the (x,y) position of the cursor on the screen.
  var span = domHelper.$(':' + cursorPos);
  var pageOffsetTop = goog.style.getPageOffsetTop(span);
  var expectedPageOffsetTop;
  if (isTop) {
    expectedPageOffsetTop = -10000;
  } else {
    var height = goog.style.getSize(contentEditableEl).height;
    // Assume the line height to be 15px and include another 8px for padding.
    expectedPageOffsetTop = -10000 + height - 23;
  }

  // If expectedPageOffsetTop is within 2 pixels of the actual pageOffsetTop,
  // then consider that a match.
  var result = (Math.abs(pageOffsetTop - expectedPageOffsetTop) <= 2);
  goog.dom.removeNode(div);
  return result;
};


/**
 * The naive approach of creating a Range for the node and doing Range.toString()
 * converts &lt;br> elements to spaces instead of newlines, so custom logic is
 * needed.
 * @param {Node} node
 * @param {Array.<string>=} buffer
 * @return {string}
 */
dk.cursorManager.nodeToText = function(node, buffer) {
  var buffer = buffer || [];
  for (var child = node.firstChild; child; child = child.nextSibling) {
    if (child.nodeType == goog.dom.NodeType.TEXT) {
      buffer.push(child.nodeValue);
    } else if (child.nodeType == goog.dom.NodeType.ELEMENT) {
      if (child.nodeName == 'BR') {
        buffer.push('\n');
      } else {
        dk.cursorManager.nodeToText(child, buffer);
      }
    }
  }
  return buffer.join('');
};
